package com.google.code.stalkernet.example.ui;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.net.SocketAddress;
import java.nio.channels.Selector;
import java.util.HashSet;
import java.util.Set;

import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import com.google.code.stalkernet.advertisements.Advertisements;
import com.google.code.stalkernet.advertisements.SimpleAdvertisements;
import com.google.code.stalkernet.async.Async;
import com.google.code.stalkernet.async.factory.AsyncSocketAddressPeerFactory;
import com.google.code.stalkernet.async.factory.AsyncSocketChannelPeerFactory;
import com.google.code.stalkernet.database.Database;
import com.google.code.stalkernet.database.InMemoryDatabase;
import com.google.code.stalkernet.example.ui.util.SocketAddressUtils;
import com.google.code.stalkernet.node.Node;
import com.google.code.stalkernet.node.SimpleNode;
import com.google.code.stalkernet.peer.Peer;
import com.google.code.stalkernet.reactor.SelectorReactor;
import com.google.code.stalkernet.server.ReactorServer;
import com.google.code.stalkernet.server.Server;
import com.google.code.stalkernet.service.AdvertisementConnectorService;
import com.google.code.stalkernet.service.DatabasePersistanceService;
import com.google.code.stalkernet.service.ServerAdvertiserService;

public class StalkerNetUI implements Runnable
{
    private final File dir;
    private final SocketAddress [] ports;
    private final SocketAddress [] peers;

    private final SelectorReactor reactor;
    private final Database database;
    private final Advertisements incomingAdvertisements;
    private final Advertisements outgoingAdvertisements;
    private final Node node;
    private final AsyncSocketAddressPeerFactory asyncSocketAddressPeerFactory;
    private final Server server;
    private final MainFrame mainFrame;

    private final DatabasePersistanceService databasePersistanceService;
    private final ServerAdvertiserService serverAdvertiserService;
    private final AdvertisementConnectorService advertisementConnectorService;

    public StalkerNetUI (
        File dir, SocketAddress [] ports, SocketAddress [] peers)
        throws Exception
    {
        if (dir == null)
            throw new IllegalArgumentException ("Dir is null");

        if (ports == null)
            throw new IllegalArgumentException ("Ports is null");

        if (peers == null)
            throw new IllegalArgumentException ("Peers is null");

        this.dir = dir;
        this.ports = ports.clone ();
        this.peers = peers.clone ();

        reactor = new SelectorReactor (Selector.open ());

        database = new InMemoryDatabase ();
        incomingAdvertisements = new SimpleAdvertisements ();
        outgoingAdvertisements = new SimpleAdvertisements ();
        node = new SimpleNode (
            database, incomingAdvertisements, outgoingAdvertisements);

        AsyncSocketChannelPeerFactory asyncSocketChannelPeerFactory =
            new AsyncSocketChannelPeerFactory (node.getID (), reactor);

        asyncSocketAddressPeerFactory =
            new AsyncSocketAddressPeerFactory (
                asyncSocketChannelPeerFactory, reactor);

        server = new ReactorServer (
            reactor, asyncSocketChannelPeerFactory, node);
        mainFrame = new MainFrame (
            reactor, database, server, asyncSocketAddressPeerFactory, node,
            incomingAdvertisements, outgoingAdvertisements);

        databasePersistanceService =
            new DatabasePersistanceService (
                new File (dir, "database.bin"), database);
        serverAdvertiserService =
            new ServerAdvertiserService (
                server, incomingAdvertisements, node.getID ());
        advertisementConnectorService =
            new AdvertisementConnectorService (
                reactor, incomingAdvertisements, outgoingAdvertisements,
                asyncSocketAddressPeerFactory, node);
    }

    @Override
    public void run ()
    {
        try
        {
            UIManager.setLookAndFeel (
                UIManager.getSystemLookAndFeelClassName ());
        }
        catch (Exception ex)
        {
            ex.printStackTrace ();
        }

        for (SocketAddress port: ports)
            server.addSocketAddress (port);

        dir.mkdirs ();

        databasePersistanceService.start ();
        serverAdvertiserService.start ();
        advertisementConnectorService.start ();

        for (SocketAddress peer: peers)
            asyncSocketAddressPeerFactory.produce (
                peer, new Async.Callback <Peer> ()
                {

                    @Override
                    public void onSuccess (Peer result)
                    {
                        node.addPeer (result);
                    }

                    @Override
                    public void onError (Throwable error)
                    {
                        // Do nothing
                    }
                });

        mainFrame.pack ();
        mainFrame.addWindowListener (new WindowAdapter()
        {
            @Override
            public void windowClosed (WindowEvent e)
            {
                reactor.shutdown ();
                mainFrame.dispose ();
                advertisementConnectorService.stop ();
                serverAdvertiserService.stop ();
                databasePersistanceService.stop ();
            }
        });
        mainFrame.setVisible (true);

        new Thread (reactor, "Reactor").start ();
    }

    public static void main (String [] args) throws Exception
    {
        Set <SocketAddress> ports = new HashSet <SocketAddress> ();
        Set <SocketAddress> peers = new HashSet <SocketAddress> ();
        String dir = null;

        int state = 0;

        for (String arg: args)
        {
            switch (state)
            {
            case 0: // Initial
                if ("-p".equals (arg)) state = 1;
                else if ("-c".equals (arg)) state = 2;
                else if ("--".equals (arg)) state = 3;
                else if (arg.startsWith ("-"))
                    throw new IllegalArgumentException ("Unknown flag: " + arg);
                else
                {
                    if (dir != null)
                        throw new IllegalArgumentException ("Dir already set");

                    dir = arg;

                    state = 3;
                }
                break;
            case 1: // Port
                ports.add (SocketAddressUtils.parseSocketAddress (arg));
                state = 0;
                break;
            case 2: // Peer
                peers.add (SocketAddressUtils.parseSocketAddress (arg));
                state = 0;
                break;
            case 3: //
                if (dir != null)
                    throw new IllegalArgumentException ("Dir already set");

                dir = arg;

                break;
            default:
                throw new Error ("Impossible");
            }
        }

        StalkerNetUI stalkerNetUI = new StalkerNetUI (
            new File (dir),
            ports.toArray (new SocketAddress [ports.size ()]),
            peers.toArray (new SocketAddress [peers.size ()]));
        SwingUtilities.invokeLater (stalkerNetUI);
    }
}
